Skip to content

Add local dev folder to POCv2 - #19

Merged
Pijukatel merged 11 commits into
v2-poc-requirementsfrom
claude/actor-runtime-bind-mount-0xgwph
Aug 20, 2026
Merged

Add local dev folder to POCv2#19
Pijukatel merged 11 commits into
v2-poc-requirementsfrom
claude/actor-runtime-bind-mount-0xgwph

Conversation

@Pijukatel

@Pijukatel Pijukatel commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

This PR adds an option to run code changes as quickly as possible.
It allows the user to bind-mount the local development folder into the already build docker container and run the code updated from the local dev folder.

Compiled/non compiled difference

For Python or JS actors

  • change code
  • run actor

For TS actors

  • change code
  • compile
  • run actor

Workflow change

The old Apify workflow:

  • apify push (rebuilds the image)
  • apify call (run the actor)
  • MAKE CODE CHANGE
  • apify push (rebuilds the image)
  • apify call (run the actor)
  • MAKE CODE CHANGE
  • apify push (rebuilds the image)
  • apify call (run the actor)

The Actor runtime workflow:

  • apify push (rebuilds the image)

  • apify call (run the actor)

  • MAKE CODE CHANGE

  • set up dev folder (through cli+API or frontend form)
    (No more building !!!!!)

  • apify call (run the actor)

  • MAKE CODE CHANGE

  • apify call (run the actor)

Limitations

  • Environment change requires a Docker image rebuild (package changes, Dockerfile changes, ....)

claude and others added 11 commits August 19, 2026 07:18
Register an Actor's host source folder and mount it over the image working
directory on every run, so a local `tsc` is enough to pick up TS changes.

The folder is registered through a new local-only endpoint outside the
emulated Apify /v2 surface, and through a form on the console Actor page.
Registration verifies the folder exists host-side via a create-only probe
container, since the runtime is itself containerized and cannot stat a host
path. Mounts (not Binds) is used throughout so a missing source is an error
rather than an auto-created empty directory masking the working directory.

Requirements updated to match: dockerode instead of a shelled-out docker
inspect, --mount semantics instead of -v, the new endpoint namespace, and the
console no longer being strictly view-only.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
Code comments cited files that are gitignored, so they were dead pointers for
anyone reading the repository. They now cite moby/dockerode behaviour in its
own terms or the committed requirements sections instead. The reasoning is
unchanged; only the references are.

Probe image resolution moves onto a shared resolveTaggedBuild helper, also now
used by the run-start path it was duplicating. This drops an arbitrary-tag
fallback: an Actor whose only build is not tagged latest is refused at
registration, matching what a tag-less run already does rather than succeeding
against a build no run could reach.

Adds coverage for the build-time working-directory persist, both when the
build reports one and when it does not and a stored value must survive.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
A tag whose build record was deleted reported "Actor has no build tagged X",
which was wrong: the tag exists. Build resolution now reports the two cases
apart, both still 404 record-not-found, and both are covered by tests.

The default build tag becomes one exported constant that the run route and the
run service import, replacing three hand-synced 'latest' literals.

Adds a services-layer test that a run's mount is derived from the Actor's
stored fields, covering both set and cleared.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
…edAt

Registering a dev folder bumped the Actor's modifiedAt, which is exposed on
/v2 -- the emulated API could observe a purely local registration. Dev-folder
writes no longer touch it, and clearing an unregistered Actor writes nothing.

The dev-folder pipeline moves out of the actors service into its own module,
which lets it reuse the builds service instead of duplicating a registry read
to avoid a cycle. HTTP statuses and error types move to the route layer.

A tag whose build was deleted reports the same bare not-found the run route
gave before this branch, so run-start behaviour is unchanged throughout.

Probe containers are labelled and swept by orphan reconciliation, the driver's
probe method is required rather than optional, whitespace-only paths are
rejected instead of silently clearing, console styles move into the shared
template, and the e2e test copies the sample Actor instead of editing it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
Sweeping probe containers alongside run containers put both label keys in one
listContainers filter, but the daemon requires a container to carry every key
given in a single call, so the query matched nothing and reconciliation stopped
reclaiming orphaned run containers -- and their anonymous volumes with them.
Each label key now gets its own query and the results are unioned by id.

The stub in the unit test now models that daemon-side behaviour, so a combined
call returns nothing and fails, and a further test pins one value per call.

The dev-folder route module regains the mount(router, deps) shape its siblings
use, with the server owning the /actor-runtime prefix as it owns /v2.

Also corrects the rejection message for an Actor whose build is not tagged
latest, and the requirements and comments that repeated its framing.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
The test installed dependencies into the folder it then bind-mounted, so the
Actor resolved its imports from the host copy and the run would have passed
whether or not the image's node_modules survived the mount. The copied folder
now has node_modules removed before registration, so resolution can only come
through the anonymous volume.

Types the dockerode stubs in the driver unit test, which had drifted to casting
empty mock-call tuples; the build does not cover test files, so this went
unnoticed.

Documents the probe label and its startup sweep, records that host-level volume
and container invariants may be read from the Docker CLI since no Apify command
observes them, and corrects the claim that the runtime is always containerized.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
startRuntimeContainer binds fixed host ports, and vitest runs test files in
parallel by default, so the two e2e files raced for 3333/3000 and whichever
lost died on "port is already allocated". test:e2e now disables file
parallelism, and the port-binding site says why so a third file does not
rediscover it. The unit and integration suites keep running in parallel.

Also corrects the driver doc that still claimed one bind-mount site, drops a
cast the stub's own parameter type makes unnecessary, states the registration
precondition in the README as the latest tag rather than any build, and rewraps
a CLAUDE.md bullet that split an inline code span.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
Docker accepts a regular file as a bind source, so a file path passed the
dev-folder check and only failed later as a broken run. The probe now binds
<path>/. , which the daemon rejects for a file while still reporting a missing
path as missing, and the route answers with a message naming the real cause.

The image working directory moves from the Actor to the build that produced it,
and a run mounts against the working directory of the build it resolved. An
Actor with several tags could otherwise mount at a path taken from a different
tag's image while the console reported the mount as applied.

The endpoint is also served under /v2 so it can be reached without a ../ prefix,
since apify api builds its URLs onto a /v2 base. Both mounts share one handler
and one auth pass; the documented form drops the dots.

Adds an end-to-end case for the workflow this exists for: two runs of the same
build, recompiled in between, with no push and no rebuild.

Formats CLAUDE.md so format:check passes, and forces the end-to-end re-pushes,
which apify push refuses once a build has bumped the Actor past the pushed
files' timestamps.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
The bind-mount sections described how today's code works -- library calls,
Docker API field names, the daemon's own error strings, internal file names --
rather than what is required. They now state outcomes: the path must be
absolute, must exist on the host, and must be a directory; being unable to
verify is never reported as missing; the mount applies only when the folder and
the resolved build's working directory are both known; a folder that has gone
away fails the run visibly; the image's dependencies survive the mount.

The constraint that the runtime cannot judge a host path from its own
filesystem stays, without prescribing the workaround.

Restores the note that the console is no longer strictly view-only, which the
previous trim dropped while the console does write.

Comments and test titles quoting the old wording are repointed. Formats api.md
so format:check passes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
Registration needed an image only to create the throwaway container that asks
the daemon whether the submitted path is a mountable directory. Borrowing the
Actor's latest build for that made registration depend on a prior build and
turned a folder problem into a message about build tags, while the image itself
contributes nothing to the check.

The driver now builds and owns a minimal image for that purpose, so a folder can
be registered against an Actor that has never been built.

The reported status is now just the registered folder. Whether a mount applies
depends on which build a run resolves, so an Actor-level field could not answer
it honestly.

The tag-less run default is untouched -- that belongs to the run route, not to
this feature.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011L4VcFqN9UZUbVRMvQSugv
@Pijukatel
Pijukatel merged commit 829ef03 into v2-poc-requirements Aug 20, 2026
2 checks passed
@Pijukatel
Pijukatel deleted the claude/actor-runtime-bind-mount-0xgwph branch August 20, 2026 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants